﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using boda.utilities.Extensions;
using boda.utilities.Times;

using Microsoft.AspNetCore.Http;

namespace boda.utilities.Sockets
{
    public class WebSocketServerHelper : IDisposable
    {
        private List<CancellationTokenSource> _StoppingTokens = new List<CancellationTokenSource>();
        private ConcurrentDictionary<string, WebSocketModel> _clients = new ConcurrentDictionary<string, WebSocketModel>();
        private Task<Task?>? _heatbeatCheckTask;
        public WebSocketServerHelper()
        {
            //StartHbCheckTask();
        }
        public async Task HandleWebSocketRequest(HttpContext context)
        {
            if (!context.WebSockets.IsWebSocketRequest)
            {
                context.Response.StatusCode = 400;
                return;
            }
            var ip = context.Connection.LocalIpAddress?.MapToIPv4()?.ToString();
            var identity = ip + context.Request.Path.ToString();
            await Console.Out.WriteLineAsync($"{identity} connects to server!");

            var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            var key = webSocket.GetHashCode().ToString();
            WebSocketModel webSocketModel = new WebSocketModel(webSocket);
            webSocketModel.RoleLevel = WSRoleLevel.Worker;
            webSocketModel.Name = key;
            webSocketModel.HbTimestamp = DateTimeCom.GetTimeStamp();
            _clients.TryAdd(key, webSocketModel);

            // 开启新线程，阻塞读取数据
            await ReceiveMessages(webSocketModel, key);
            // Start a new thread to handle incoming messages from the WebSocket
            // 开启新线程，会导致socket客户端被abort掉
            //Thread receiveThread = new(() => ReceiveMessages(webSocket));
            //receiveThread.Start();
        }

        private void StartHbCheckTask()
        {
            _StoppingTokens.Add(item: new CancellationTokenSource());
            _heatbeatCheckTask = new Task<Task?>(() =>
            {
                CancellationTokenSource StoppingToken = _StoppingTokens.Last();
                return Task.Factory.StartNew(async (a) =>
                {
                    while (!StoppingToken.IsCancellationRequested)
                    {
                        foreach (KeyValuePair<string, WebSocketModel> item in _clients)
                        {
                            WebSocketModel socketModel = item.Value;
                            var nowTimestamp = DateTimeCom.GetTimeStamp();
                            var diff = nowTimestamp - socketModel.HbTimestamp;
                            if (diff > 240)
                            {
                                //await Console.Out.WriteLineAsync($"{socketModel.Name} 超过20秒没有心跳，将会删除客户端连接");
                                try
                                {
                                    await socketModel.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                                }
                                catch (Exception ex)
                                {
                                    await Console.Out.WriteLineAsync(ex.Message);
                                }
                                finally
                                {
                                    _clients.TryRemove(item.Key, out _);
                                }
                            }
                            else
                            {
                                //await Console.Out.WriteLineAsync($"{socketModel.Name} 存在心跳");
                            }
                        }

                        await Task.Delay(5000);
                    }
                }, StoppingToken.Token, TaskCreationOptions.LongRunning);
            });
            _heatbeatCheckTask.Start();
        }

        private void StopHbCheckTask()
        {
            CancellationTokenSource StoppingToken = _StoppingTokens.LastOrDefault();
            if (StoppingToken != null)
            {
                StoppingToken?.Cancel();
                var devResult = _heatbeatCheckTask?.Result;
                if (devResult?.Status != TaskStatus.Canceled)
                {
                    if (devResult?.Wait(5000) == true)
                    {
                        Console.WriteLine($"服务端心跳检查任务已经停止");
                    }
                    else
                    {
                        Console.WriteLine($"服务端心跳检查任务已经超时");
                    }
                }
                _heatbeatCheckTask?.Dispose();
                _heatbeatCheckTask = null;
                if (StoppingToken != null)
                {
                    _StoppingTokens.Remove(StoppingToken);
                }
            }
        }

        private async Task ReceiveMessages(WebSocketModel webSocket, string key)
        {
            var client = webSocket.WebSocket;
            try
            {
                var buffer = new byte[1024];
                WebSocketReceiveResult result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                while (!result.CloseStatus.HasValue)
                {
                    string msg = Encoding.Default.GetString(buffer, 0, result.Count)?.Trim();
                    if (client.State == WebSocketState.Open)
                    {
                        HandleMessage(webSocket, msg, key);
                    }

                    buffer = new byte[1024];
                    result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                }
            }
            catch (Exception ex)
            {
                await client?.CloseAsync(WebSocketCloseStatus.NormalClosure, ex.Message, CancellationToken.None);
            }
            finally
            {
                _clients.TryRemove(webSocket.Name, out _);
            }

        }

        private void HandleMessage(WebSocketModel webSocket, string message, string key)
        {
            //Console.WriteLine($"服务端接收到数据:{message}");
            webSocket.HbTimestamp = DateTimeCom.GetTimeStamp();

            var client = webSocket.WebSocket;

            WsMsgFrame? frame;
            try
            {
                frame = message.ToWsMsgFrame();
            }
            catch
            {
                frame = new WsMsgFrame();
                frame.Opcode = WSDataType.Error;
            }

            switch (frame!.Opcode)
            {
                case WSDataType.None:
                    break;
                case WSDataType.Error:
                    {
                        WsMsgFrame rsp = new WsMsgFrame();
                        rsp.HashVal = webSocket.WebSocket.GetHashCode().ToString();
                        rsp.Opcode = WSDataType.Error;
                        rsp.Msg = new WSDataMsg("数据错误", $"数据:{message},帧格式不对,需要按照WsMsgFrame的Json格式发送数据");
                        client.SendAsync(new ArraySegment<byte>(rsp.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    break;
                case WSDataType.Request: // 单一请求
                    foreach (var clientItem in _clients.Values)
                    {
                        // 转发给所在组别的管理员（每个组，只有一个管理员），服务端根据HashVal，取确认请求，并Response中含有请求这的HashVal
                        if (clientItem.RoleLevel == WSRoleLevel.Admin && clientItem.Group == webSocket.Group)
                        {
                            frame.RoleLevel = clientItem.RoleLevel;
                            clientItem.WebSocket.SendAsync(new ArraySegment<byte>(frame.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                    break;
                case WSDataType.Response: // 单一回复
                    foreach (var clientItem in _clients.Values)
                    {
                        // 根据唯一的HashVal，转发给请求的客户端
                        if (clientItem.RoleLevel != WSRoleLevel.Admin && clientItem.Name == frame.HashVal)
                        {
                            frame.RoleLevel = clientItem.RoleLevel;
                            clientItem.WebSocket.SendAsync(new ArraySegment<byte>(frame.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                    break;
                case WSDataType.Broadcast: // 广播给所在组别的所有客户端
                    {
                        // 发送自己所在组别的其他客户端
                        frame.RoleLevel = WSRoleLevel.None;
                        frame.Group = webSocket.Group;
                        foreach (var clientItem in _clients.Values)
                        {
                            if (webSocket.Name != clientItem.Name && clientItem.Group == webSocket.Group)
                            {
                                frame.HashVal = clientItem.Name;
                                clientItem.WebSocket.SendAsync(new ArraySegment<byte>(frame.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                            }
                        }
                    }
                    break;
                case WSDataType.Close: // 通知所有客户端关闭
                    {
                        frame.RoleLevel = WSRoleLevel.None;
                        frame.Group = webSocket.Group;
                        frame.Msg = new WSDataMsg("服务端关闭", "WebSocket server begins to close, so close your self");
                        foreach (var clientItem in _clients.Values)
                        {
                            frame.HashVal = clientItem.Name;
                            clientItem.WebSocket.SendAsync(new ArraySegment<byte>(frame.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                    break;
                case WSDataType.Ping:
                    {
                        webSocket.HbTimestamp = DateTimeCom.GetTimeStamp();

                        WsMsgFrame rsp = new WsMsgFrame();
                        rsp.Opcode = WSDataType.Pong;
                        rsp.Msg = new WSDataMsg("Ping", "Pong");
                        rsp.HashVal = webSocket.Name;
                        rsp.RoleLevel = webSocket.RoleLevel;
                        rsp.Group = webSocket.Group;
                        client.SendAsync(new ArraySegment<byte>(rsp.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    break;
                case WSDataType.Pong:
                    break;
                case WSDataType.Register:
                    {
                        webSocket.RoleLevel = frame.RoleLevel;
                        webSocket.Group = frame.Group;
                        webSocket.Name = webSocket.WebSocket.GetHashCode().ToString();

                        WsMsgFrame rsp = new WsMsgFrame();
                        rsp.Opcode = WSDataType.Register;
                        rsp.Msg = new WSDataMsg("Register", $"1");
                        rsp.HashVal = webSocket.Name;
                        rsp.RoleLevel = webSocket.RoleLevel;
                        rsp.Group = webSocket.Group;
                        client.SendAsync(new ArraySegment<byte>(rsp.Serialize()), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    break;
                default:
                    break;
            }

        }

        public void Dispose()
        {
            StopHbCheckTask();
        }
    }
}
